/*jshint smarttabs:true, eqeqeq:false, eqnull:true, laxbreak:true*/

(function(window, document, jqLite, vx, undefined) {
	// To allow non strict code to be concatenated together with vx into a single file
	'use strict';

	////////////////////////////////
	// Filters
	///////////////////////////////

	function vxSetupFilters() {
		var mod = vx.module('v');
		/**
		 * @vxdoc filter
		 * @name vx.filter:lowercase
		 * @function
		 * @description
		 * Converts string to lowercase.
		 * @see vx.lowercase
		 */
		mod.filter('lowercase', valueFn(lowercase));
		/**
		 * @vxdoc filter
		 * @name vx.filter:uppercase
		 * @function
		 * @description
		 * Converts string to uppercase.
		 * @see vx.uppercase
		 */
		mod.filter('uppercase', valueFn(uppercase));

		mod.filter('currency', currencyFilter);
		mod.filter('date', dateFilter);
		mod.filter('filter', filterFilter);
		mod.filter('json', jsonFilter);
		mod.filter('limitTo', limitToFilter);
		mod.filter('number', numberFilter);
		mod.filter('orderBy', orderByFilter);
	}

	/**
	 * @vxdoc filter
	 * @name vx.filter:filter
	 * @function
	 *
	 * @description
	 * Selects a subset of items from `array` and returns it as a new array.
	 *
	 * Note: This function is used to augment the `Array` type in VX expressions. See
	 * {@link vx.$filter} for more information about VX arrays.
	 *
	 * @param {Array} array The source array.
	 * @param {string|Object|function()} expression The predicate to be used for selecting items from
	 *   `array`.
	 *
	 *   Can be one of:
	 *
	 *   - `string`: Predicate that results in a substring match using the value of `expression`
	 *     string. All strings or objects with string properties in `array` that contain this string
	 *     will be returned. The predicate can be negated by prefixing the string with `!`.
	 *
	 *   - `Object`: A pattern object can be used to filter specific properties on objects contained
	 *     by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
	 *     which have property `name` containing "M" and property `phone` containing "1". A special
	 *     property name `$` can be used (as in `{$:"text"}`) to accept a match against any
	 *     property of the object. That's equivalent to the simple substring match with a `string`
	 *     as described above.
	 *
	 *   - `function`: A predicate function can be used to write arbitrary filters. The function is
	 *     called for each element of `array`. The final result is an array of those elements that
	 *     the predicate returned true for.
	 *
	 * @param {function(expected, actual)|true|undefined} comparator Comparator which is used in
	 *     determining if the expected value (from the filter expression) and actual value (from
	 *     the object in the array) should be considered a match.
	 *
	 *   Can be one of:
	 *
	 *     - `function(expected, actual)`:
	 *       The function will be given the object value and the predicate value to compare and
	 *       should return true if the item should be included in filtered result.
	 *
	 *     - `true`: A shorthand for `function(expected, actual) { return vx.equals(expected, actual)}`.
	 *       this is essentially strict comparison of expected and actual.
	 *
	 *     - `false|undefined`: A short hand for a function which will look for a substring match in case
	 *       insensitive way.
	 *
	 * @example
	 <doc:example>
	 <doc:source>
	 <div v-init="friends = [{name:'John', phone:'555-1276'},
	 {name:'Mary', phone:'800-BIG-MARY'},
	 {name:'Mike', phone:'555-4321'},
	 {name:'Adam', phone:'555-5678'},
	 {name:'Julie', phone:'555-8765'},
	 {name:'Juliette', phone:'555-5678'}]"></div>

	 Search: <input v-model="searchText">
	 <table id="searchTextResults">
	 <tr><th>Name</th><th>Phone</th></tr>
	 <tr v-repeat="friend in friends | filter:searchText">
	 <td>{{friend.name}}</td>
	 <td>{{friend.phone}}</td>
	 </tr>
	 </table>
	 <hr>
	 Any: <input v-model="search.$"> <br>
	 Name only <input v-model="search.name"><br>
	 Phone only <input v-model="search.phone"><br>
	 Equality <input type="checkbox" v-model="strict"><br>
	 <table id="searchObjResults">
	 <tr><th>Name</th><th>Phone</th></tr>
	 <tr v-repeat="friend in friends | filter:search:strict">
	 <td>{{friend.name}}</td>
	 <td>{{friend.phone}}</td>
	 </tr>
	 </table>
	 </doc:source>
	 <doc:scenario>
	 it('should search across all fields when filtering with a string', function() {
	 input('searchText').enter('m');
	 expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
	 toEqual(['Mary', 'Mike', 'Adam']);

	 input('searchText').enter('76');
	 expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
	 toEqual(['John', 'Julie']);
	 });

	 it('should search in specific fields when filtering with a predicate object', function() {
	 input('search.$').enter('i');
	 expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')).
	 toEqual(['Mary', 'Mike', 'Julie', 'Juliette']);
	 });
	 it('should use a equal comparison when comparator is true', function() {
	 input('search.name').enter('Julie');
	 input('strict').check();
	 expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')).
	 toEqual(['Julie']);
	 });
	 </doc:scenario>
	 </doc:example>
	 */
	function filterFilter() {
		return function(array, expression, comperator) {
			if (!vx.isArray(array))
				return array;
			var predicates = [];
			predicates.check = function(value) {
				for (var j = 0; j < predicates.length; j++) {
					if (!predicates[j](value)) {
						return false;
					}
				}
				return true;
			};
			switch(typeof comperator) {
				case "function":
					break;
				case "boolean":
					if (comperator === true) {
						comperator = function(obj, text) {
							return vx.equals(obj, text);
						};
						break;
					}
				/* falls through */
				default:
					comperator = function(obj, text) {
						text = ('' + text).toLowerCase();
						return ('' + obj).toLowerCase().indexOf(text) > -1;
					};
			}
			var search = function(obj, text) {
				if ( typeof text == 'string' && text.charAt(0) === '!') {
					return !search(obj, text.substr(1));
				}
				switch (typeof obj) {
					case "boolean":
					case "number":
					case "string":
						return comperator(obj, text);
					case "object":
						if (( typeof text) == "object")
							return comperator(obj, text);
						for (var objKey in obj) {
							if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
								return true;
							}
						}
						return false;
					case "array":
						for (var i = 0; i < obj.length; i++) {
							if (search(obj[i], text)) {
								return true;
							}
						}
						return false;
					default:
						return false;
				}
			};
			/*jshint loopfunc:true*/
			switch (typeof expression) {
				case "boolean":
				case "number":
				case "string":
					expression = {
						$ : expression
					};
				/* falls through */
				case "object":
					for (var key in expression) {
						if (key == '$') {
							(function() {
								if (!expression[key])
									return;
								var path = key;
								predicates.push(function(value) {
									return search(value, expression[path]);
								});
							})();
						} else {
							(function() {
								if (!expression[key])
									return;
								var path = key;
								predicates.push(function(value) {
									return search(getter(value, path), expression[path]);
								});
							})();
						}
					}
					break;
				case 'function':
					predicates.push(expression);
					break;
				default:
					return array;
			}
			var filtered = [];
			for (var j = 0; j < array.length; j++) {
				var value = array[j];
				if (predicates.check(value)) {
					filtered.push(value);
				}
			}
			return filtered;
		};
	}

	/**
	 * @vxdoc filter
	 * @name vx.filter:currency
	 * @function
	 *
	 * @description
	 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
	 * symbol for current locale is used.
	 *
	 * @param {number} amount Input to filter.
	 * @param {string=} symbol Currency symbol or identifier to be displayed.
	 * @returns {string} Formatted number.
	 *
	 *
	 * @example
	 <doc:example>
	 <doc:source>
	 <script>
	 function Ctrl($scope) {
	 $scope.amount = 1234.56;
	 }
	 </script>
	 <div v-controller="Ctrl">
	 <input type="number" v-model="amount"> <br>
	 default currency symbol ($): {{amount | currency}}<br>
	 custom currency identifier (USD$): {{amount | currency:"USD$"}}
	 </div>
	 </doc:source>
	 <doc:scenario>
	 it('should init with 1234.56', function() {
	 expect(binding('amount | currency')).toBe('$1,234.56');
	 expect(binding('amount | currency:"USD$"')).toBe('USD$1,234.56');
	 });
	 it('should update', function() {
	 input('amount').enter('-1234');
	 expect(binding('amount | currency')).toBe('($1,234.00)');
	 expect(binding('amount | currency:"USD$"')).toBe('(USD$1,234.00)');
	 });
	 </doc:scenario>
	 </doc:example>
	 */
	currencyFilter.$inject = ['$locale'];
	function currencyFilter($locale) {
		var formats = $locale.NUMBER_FORMATS;
		return function(amount, currencySymbol) {
			if (vx.isUndefined(currencySymbol))
				currencySymbol = formats.CURRENCY_SYM;
			return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2).replace(/\u00A4/g, currencySymbol);
		};
	}

	function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
		if (isNaN(number) || !isFinite(number))
			return '';

		var isNegative = number < 0;
		number = Math.abs(number);
		var numStr = number + '', formatedText = '', parts = [];

		var hasExponent = false;
		if (numStr.indexOf('e') !== -1) {
			var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
			if (match && match[2] == '-' && match[3] > fractionSize + 1) {
				numStr = '0';
			} else {
				formatedText = numStr;
				hasExponent = true;
			}
		}
		if (!hasExponent) {
			var fractionLen = (numStr.split(decimalSep)[1] || '').length;

			// determine fractionSize if it is not specified
			if (vx.isUndefined(fractionSize)) {
				fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac);
			}

			var pow = Math.pow(10, fractionSize);
			number = Math.round(number * pow) / pow;
			var fraction = ('' + number).split(decimalSep);
			var whole = fraction[0];
			fraction = fraction[1] || '';

			var pos = 0, lgroup = pattern.lgSize, group = pattern.gSize, i;

			if (whole.length >= (lgroup + group)) {
				pos = whole.length - lgroup;
				for ( i = 0; i < pos; i++) {
					if ((pos - i) % group === 0 && i !== 0) {
						formatedText += groupSep;
					}
					formatedText += whole.charAt(i);
				}
			}

			for ( i = pos; i < whole.length; i++) {
				if ((whole.length - i) % lgroup === 0 && i !== 0) {
					formatedText += groupSep;
				}
				formatedText += whole.charAt(i);
			}

			// format fraction part.
			while (fraction.length < fractionSize) {
				fraction += '0';
			}

			if (fractionSize && fractionSize !== "0")
				formatedText += decimalSep + fraction.substr(0, fractionSize);
		}

		parts.push( isNegative ? pattern.negPre : pattern.posPre);
		parts.push(formatedText);
		parts.push( isNegative ? pattern.negSuf : pattern.posSuf);
		return parts.join('');
	}

	/**
	 * @vxdoc filter
	 * @name vx.filter:number
	 * @function
	 *
	 * @description
	 * Formats a number as text.
	 *
	 * If the input is not a number an empty string is returned.
	 *
	 * @param {number|string} number Number to format.
	 * @param {(number|string)=} [fractionSize=2] Number of decimal places to round the number to.
	 * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit.
	 *
	 * @example
	 <doc:example>
	 <doc:source>
	 <script>
	 function Ctrl($scope) {
	 $scope.val = 1234.56789;
	 }
	 </script>
	 <div v-controller="Ctrl">
	 Enter number: <input v-model='val'><br>
	 Default formatting: {{val | number}}<br>
	 No fractions: {{val | number:0}}<br>
	 Negative number: {{-val | number:4}}
	 </div>
	 </doc:source>
	 <doc:scenario>
	 it('should format numbers', function() {
	 expect(binding('val | number')).toBe('1,234.568');
	 expect(binding('val | number:0')).toBe('1,235');
	 expect(binding('-val | number:4')).toBe('-1,234.5679');
	 });

	 it('should update', function() {
	 input('val').enter('3374.333');
	 expect(binding('val | number')).toBe('3,374.333');
	 expect(binding('val | number:0')).toBe('3,374');
	 expect(binding('-val | number:4')).toBe('-3,374.3330');
	 });
	 </doc:scenario>
	 </doc:example>
	 */

	numberFilter.$inject = ['$locale'];
	function numberFilter($locale) {
		var formats = $locale.NUMBER_FORMATS;
		return function(number, fractionSize) {
			return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize);
		};
	}

	/**
	 * @vxdoc filter
	 * @name vx.filter:date
	 * @function
	 *
	 * @description
	 *   Formats `date` to a string based on the requested `format`.
	 *
	 *   `format` string can be composed of the following elements:
	 *
	 *   * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
	 *   * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
	 *   * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
	 *   * `'MMMM'`: Month in year (January-December)
	 *   * `'MMM'`: Month in year (Jan-Dec)
	 *   * `'MM'`: Month in year, padded (01-12)
	 *   * `'M'`: Month in year (1-12)
	 *   * `'dd'`: Day in month, padded (01-31)
	 *   * `'d'`: Day in month (1-31)
	 *   * `'EEEE'`: Day in Week,(Sunday-Saturday)
	 *   * `'EEE'`: Day in Week, (Sun-Sat)
	 *   * `'HH'`: Hour in day, padded (00-23)
	 *   * `'H'`: Hour in day (0-23)
	 *   * `'hh'`: Hour in am/pm, padded (01-12)
	 *   * `'h'`: Hour in am/pm, (1-12)
	 *   * `'mm'`: Minute in hour, padded (00-59)
	 *   * `'m'`: Minute in hour (0-59)
	 *   * `'ss'`: Second in minute, padded (00-59)
	 *   * `'s'`: Second in minute (0-59)
	 *   * `'.sss' or ',sss'`: Millisecond in second, padded (000-999)
	 *   * `'a'`: am/pm marker
	 *   * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
	 *
	 *   `format` string can also be one of the following predefined
	 *   {@link guide/i18n localizable formats}:
	 *
	 *   * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale
	 *     (e.g. Sep 3, 2010 12:05:08 pm)
	 *   * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US  locale (e.g. 9/3/10 12:05 pm)
	 *   * `'fullDate'`: equivalent to `'EEEE, MMMM d,y'` for en_US  locale
	 *     (e.g. Friday, September 3, 2010)
	 *   * `'longDate'`: equivalent to `'MMMM d, y'` for en_US  locale (e.g. September 3, 2010
	 *   * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US  locale (e.g. Sep 3, 2010)
	 *   * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10)
	 *   * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm)
	 *   * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm)
	 *
	 *   `format` string can contain literal values. These need to be quoted with single quotes (e.g.
	 *   `"h 'in the morning'"`). In order to output single quote, use two single quotes in a sequence
	 *   (e.g. `"h o''clock"`).
	 *
	 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
	 *    number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and its
	 *    shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is
	 *    specified in the string input, the time is considered to be in the local timezone.
	 * @param {string=} format Formatting rules (see Description). If not specified,
	 *    `mediumDate` is used.
	 * @returns {string} Formatted string or the input if input is not recognized as date/millis.
	 *
	 * @example
	 <doc:example>
	 <doc:source>
	 <span v-non-bindable>{{1288323623006 | date:'medium'}}</span>:
	 {{1288323623006 | date:'medium'}}<br>
	 <span v-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
	 {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}<br>
	 <span v-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
	 {{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}<br>
	 </doc:source>
	 <doc:scenario>
	 it('should format date', function() {
	 expect(binding("1288323623006 | date:'medium'")).
	 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
	 expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).
	 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/);
	 expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).
	 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
	 });
	 </doc:scenario>
	 </doc:example>
	 */
	dateFilter.$inject = ['$locale'];
	function dateFilter($locale) {

		var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+\-])(\d\d):?(\d\d))?)?$/;
		//                                 1          2          3           4             5            6            7              8  9      10        11
		function jsonStringToDate(string) {
			var match;
			/*jshint boss:true*/
			if ( match = string.match(R_ISO8601_STR)) {
				var date = new Date(0), tzHour = 0, tzMin = 0, //
				dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, timeSetter = match[8] ? date.setUTCHours : date.setHours;
				if (match[9]) {
					tzHour = int(match[9] + match[10]);
					tzMin = int(match[9] + match[11]);
				}
				dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3]));
				var h = int(match[4] || 0) - tzHour;
				var m = int(match[5] || 0) - tzMin;
				var s = int(match[6] || 0);
				var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
				timeSetter.call(date, h, m, s, ms);
				return date;
			}
			return string;
		}

		function dateGetter(name, size, offset, trim) {
			offset = offset || 0;
			return function(date) {
				var value = date['get' + name]();
				if (offset > 0 || value > -offset)
					value += offset;
				if (value === 0 && offset == -12)
					value = 12;
				return vx.padNumber(value, size, trim);
			};
		}

		function dateStrGetter(name, shortForm) {
			return function(date, formats) {
				var value = date['get' + name]();
				var get = vx.uppercase( shortForm ? ('SHORT' + name) : name);

				return formats[get][value];
			};
		}

		function timeZoneGetter(date) {
			var zone = -1 * date.getTimezoneOffset();
			var paddedZone = (zone >= 0) ? "+" : "";
			paddedZone += vx.padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + vx.padNumber(Math.abs(zone % 60), 2);
			return paddedZone;
		}

		function ampmGetter(date, formats) {
			return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
		}

		var DATE_FORMATS = {
			yyyy : dateGetter('FullYear', 4),
			yy : dateGetter('FullYear', 2, 0, true),
			y : dateGetter('FullYear', 1),
			MMMM : dateStrGetter('Month'),
			MMM : dateStrGetter('Month', true),
			MM : dateGetter('Month', 2, 1),
			M : dateGetter('Month', 1, 1),
			dd : dateGetter('Date', 2),
			d : dateGetter('Date', 1),
			HH : dateGetter('Hours', 2),
			H : dateGetter('Hours', 1),
			hh : dateGetter('Hours', 2, -12),
			h : dateGetter('Hours', 1, -12),
			mm : dateGetter('Minutes', 2),
			m : dateGetter('Minutes', 1),
			ss : dateGetter('Seconds', 2),
			s : dateGetter('Seconds', 1),
			// while ISO 8601 requires fractions to be prefixed with `.` or `,`
			// we can be just safely rely on using `sss` since we currently don't support single or two digit fractions
			sss : dateGetter('Milliseconds', 3),
			EEEE : dateStrGetter('Day'),
			EEE : dateStrGetter('Day', true),
			a : ampmGetter,
			Z : timeZoneGetter
		};

		var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/, NUMBER_STRING = /^\d+$/;

		return function(date, format) {
			var text = '', parts = [], fn, match;
			format = format || 'mediumDate';
			format = $locale.DATETIME_FORMATS[format] || format;
			if (isString(date)) {
				if (NUMBER_STRING.test(date)) {
					date = int(date);
				} else {
					date = jsonStringToDate(date);
				}
			}

			if (vx.isNumber(date)) {
				date = new Date(date);
			}

			if (!vx.isDate(date)) {
				return date;
			}

			while (format) {
				match = DATE_FORMATS_SPLIT.exec(format);
				if (match) {
					parts = concat(parts, match, 1);
					format = parts.pop();
				} else {
					parts.push(format);
					format = null;
				}
			}

			forEach(parts, function(value) {
				fn = DATE_FORMATS[value];
				text += fn ? fn(date, $locale.DATETIME_FORMATS) : value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
			});
			return text;
		};
	}

	/**
	 * @vxdoc filter
	 * @name vx.filter:json.json
	 * @function
	 *
	 * @description
	 *   Allows you to convert a JavaScript object into JSON string.
	 *
	 *   This filter is mostly useful for debugging. When using the double curly {{value}} notation
	 *   the binding is automatically converted to JSON.
	 *
	 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
	 * @returns {string} JSON string.
	 *
	 *
	 * @example:
	 <doc:example>
	 <doc:source>
	 <pre>{{ {'name':'value'} | json.json }}</pre>
	 </doc:source>
	 <doc:scenario>
	 it('should jsonify filtered objects', function() {
	 expect(binding("{'name':'value'}")).toMatch(/\{\n  "name": ?"value"\n}/);
	 });
	 </doc:scenario>
	 </doc:example>
	 *
	 */
	function jsonFilter() {
		return function(object) {
			return toJson(object, true);
		};
	}

	/**
	 * @vxdoc function
	 * @name vx.filter:limitTo
	 * @function
	 *
	 * @description
	 * Creates a new array or string containing only a specified number of elements. The elements
	 * are taken from either the beginning or the end of the source array or string, as specified by the
	 * value and sign (positive or negative) of `limit`.
	 *
	 * Note: This function is used to augment the `Array` type in VX expressions. See
	 * {@link vx.$filter} for more information about VX arrays.
	 *
	 * @param {Array|string} input Source array or string to be limited.
	 * @param {string|number} limit The length of the returned array or string. If the `limit` number is
	 *     positive, `limit` number of items from the beginning of the source array/string are copied.
	 *     If the number is negative, `limit` number  of items from the end of the source array/string are
	 *     copied. The `limit` will be trimmed if it exceeds `array.length`
	 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array/string had less than `limit`
	 *     elements.
	 *
	 * @example
	 <doc:example>
	 <doc:source>
	 <script>
	 function Ctrl($scope) {
	 $scope.numbers = [1,2,3,4,5,6,7,8,9];
	 $scope.letters = "abcdefghi";
	 $scope.numLimit = 3;
	 $scope.letterLimit = 3;
	 }
	 </script>
	 <div v-controller="Ctrl">
	 Limit {{numbers}} to: <input type="integer" v-model="numLimit">
	 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p>
	 Limit {{letters}} to: <input type="integer" v-model="letterLimit">
	 <p>Output letters: {{ letters | limitTo:letterLimit }}</p>
	 </div>
	 </doc:source>
	 <doc:scenario>
	 it('should limit the number array to first three items', function() {
	 expect(element('.doc-example-live input[v-model=numLimit]').val()).toBe('3');
	 expect(element('.doc-example-live input[v-model=letterLimit]').val()).toBe('3');
	 expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3]');
	 expect(binding('letters | limitTo:letterLimit')).toEqual('abc');
	 });

	 it('should update the output when -3 is entered', function() {
	 input('numLimit').enter(-3);
	 input('letterLimit').enter(-3);
	 expect(binding('numbers | limitTo:numLimit')).toEqual('[7,8,9]');
	 expect(binding('letters | limitTo:letterLimit')).toEqual('ghi');
	 });

	 it('should not exceed the maximum size of input array', function() {
	 input('numLimit').enter(100);
	 input('letterLimit').enter(100);
	 expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3,4,5,6,7,8,9]');
	 expect(binding('letters | limitTo:letterLimit')).toEqual('abcdefghi');
	 });
	 </doc:scenario>
	 </doc:example>
	 */
	function limitToFilter() {
		return function(input, limit) {
			if (!vx.isArray(input) && !vx.isString(input))
				return input;

			limit = vx.int(limit);

			if (vx.isString(input)) {
				//NaN check on limit
				if (limit) {
					return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length);
				} else {
					return "";
				}
			}

			var out = [], i, n;

			// if abs(limit) exceeds maximum length, trim it
			if (limit > input.length)
				limit = input.length;
			else if (limit < -input.length)
				limit = -input.length;

			if (limit > 0) {
				i = 0;
				n = limit;
			} else {
				i = input.length + limit;
				n = input.length;
			}

			for (; i < n; i++) {
				out.push(input[i]);
			}

			return out;
		};
	}

	/**
	 * @vxdoc function
	 * @name vx.filter:orderBy
	 * @function
	 *
	 * @description
	 * Orders a specified `array` by the `expression` predicate.
	 *
	 * Note: this function is used to augment the `Array` type in VX expressions. See
	 * {@link vx.$filter} for more information about VX arrays.
	 *
	 * @param {Array} array The array to sort.
	 * @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be
	 *    used by the comparator to determine the order of elements.
	 *
	 *    Can be one of:
	 *
	 *    - `function`: Getter function. The result of this function will be sorted using the
	 *      `<`, `=`, `>` operator.
	 *    - `string`: An VX expression which evaluates to an object to order by, such as 'name'
	 *      to sort by a property called 'name'. Optionally prefixed with `+` or `-` to control
	 *      ascending or descending sort order (for example, +name or -name).
	 *    - `Array`: An array of function or string predicates. The first predicate in the array
	 *      is used for sorting, but when two items are equivalent, the next predicate is used.
	 *
	 * @param {boolean=} reverse Reverse the order the array.
	 * @returns {Array} Sorted copy of the source array.
	 *
	 * @example
	 <doc:example>
	 <doc:source>
	 <script>
	 function Ctrl($scope) {
	 $scope.friends =
	 [{name:'John', phone:'555-1212', age:10},
	 {name:'Mary', phone:'555-9876', age:19},
	 {name:'Mike', phone:'555-4321', age:21},
	 {name:'Adam', phone:'555-5678', age:35},
	 {name:'Julie', phone:'555-8765', age:29}]
	 $scope.predicate = '-age';
	 }
	 </script>
	 <div v-controller="Ctrl">
	 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre>
	 <hr/>
	 [ <a href="" v-click="predicate=''">unsorted</a> ]
	 <table class="friend">
	 <tr>
	 <th><a href="" v-click="predicate = 'name'; reverse=false">Name</a>
	 (<a href v-click="predicate = '-name'; reverse=false">^</a>)</th>
	 <th><a href="" v-click="predicate = 'phone'; reverse=!reverse">Phone Number</a></th>
	 <th><a href="" v-click="predicate = 'age'; reverse=!reverse">Age</a></th>
	 </tr>
	 <tr v-repeat="friend in friends | orderBy:predicate:reverse">
	 <td>{{friend.name}}</td>
	 <td>{{friend.phone}}</td>
	 <td>{{friend.age}}</td>
	 </tr>
	 </table>
	 </div>
	 </doc:source>
	 <doc:scenario>
	 it('should be reverse ordered by aged', function() {
	 expect(binding('predicate')).toBe('-age');
	 expect(repeater('table.friend', 'friend in friends').column('friend.age')).
	 toEqual(['35', '29', '21', '19', '10']);
	 expect(repeater('table.friend', 'friend in friends').column('friend.name')).
	 toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']);
	 });

	 it('should reorder the table when user selects different predicate', function() {
	 element('.doc-example-live a:contains("Name")').click();
	 expect(repeater('table.friend', 'friend in friends').column('friend.name')).
	 toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']);
	 expect(repeater('table.friend', 'friend in friends').column('friend.age')).
	 toEqual(['35', '10', '29', '19', '21']);

	 element('.doc-example-live a:contains("Phone")').click();
	 expect(repeater('table.friend', 'friend in friends').column('friend.phone')).
	 toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']);
	 expect(repeater('table.friend', 'friend in friends').column('friend.name')).
	 toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']);
	 });
	 </doc:scenario>
	 </doc:example>
	 */
	orderByFilter.$inject = ['$parse'];
	function orderByFilter($parse) {
		return function(array, sortPredicate, reverseOrder) {
			if (!vx.isArray(array))
				return array;
			if (!sortPredicate)
				return array;
			sortPredicate = vx.isArray(sortPredicate) ? sortPredicate : [sortPredicate];
			sortPredicate = vx.map(sortPredicate, function(predicate) {
				var descending = false, get = predicate || identity;
				if (vx.isString(predicate)) {
					if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) {
						descending = predicate.charAt(0) == '-';
						predicate = predicate.substring(1);
					}
					get = $parse(predicate);
				}
				return reverseComparator(function(a, b) {
					return compare(get(a), get(b));
				}, descending);
			});
			var arrayCopy = [];
			for (var i = 0; i < array.length; i++) {
				arrayCopy.push(array[i]);
			}
			return arrayCopy.sort(reverseComparator(comparator, reverseOrder));

			function comparator(o1, o2) {
				for (var i = 0; i < sortPredicate.length; i++) {
					var comp = sortPredicate[i](o1, o2);
					if (comp !== 0)
						return comp;
				}
				return 0;
			}

			function reverseComparator(comp, descending) {
				return vx.toBoolean(descending) ? function(a, b) {
					return comp(b, a);
				} : comp;
			}

			function compare(v1, v2) {
				var t1 = typeof v1;
				var t2 = typeof v2;
				if (t1 == t2) {
					if (t1 == "string")
						v1 = v1.toLowerCase();
					if (t1 == "string")
						v2 = v2.toLowerCase();
					if (v1 === v2)
						return 0;
					return v1 < v2 ? -1 : 1;
				} else {
					return t1 < t2 ? -1 : 1;
				}
			}

		};
	}
})(window, document, window.$, window.vx);